package gomoku;

public class Evaluator {
	/*
	 * Default values for board markers
	 */
	int free = 0;
	int ai = 1;
	int human = 2;
	
	/* 
	 * Ratings for rows of stones
	 * Longer rows -> better rating
	 * Unbounded is better than bounded
	 */
	int[][] rowRating = {
			{0,0}, // length 0
			{0,0}, // length 1
			{40,20}, // etc
			{100,60}, // 100 for unbounded, 60 for 1 bound (two bounds is always zero)
			{300,100},
			{99999,99999}
	};
	/*
	 * The 3 shifts that give the evaluation star
	 * \   |   /
	 *  \  |  /
	 *   \ | /
	 *    \|/
	 * ----+----
	 *    /|\
	 *   / | \
	 *  /  |  \
	 * /   |   \
	 */
	int[][] shifts = {
		{1,0}, // -
		{1,-1}, // \
		{0,1} // |
	};
	
	/*
	 * Constructor
	 */
	// Could be used to update free, ai, human constants
	//public Evaluator(int cfree, int cai, int chuman) {
	//	free = cfree;
	//	ai = cai;
	//	human = chuman;
	public Evaluator() {
		
	}
	
	/*
	 * Checks if field is outside of Go-Moku board
	 */
	public boolean isOutOfBoard(int x, int y) {
		if (x <= 1 || y <= 1 || x > 15 || y > 15) {
			return true;
		} else {
			return false;
		}
	}
	
	/*
	 * Checks if a row of five stones for forPlayer is possible
	 * assumes x,y is forPlayer stone
	 */
	public boolean isRowOf5Possible(int x, int y, Board board, int[] shift, int forPlayer) {
		int count = 0;
		int againstPlayer;
		if (forPlayer == ai) {
			againstPlayer = human;
		} else {
			againstPlayer = ai;
		}
		
		// counts free and forPlayer stones on one side
		for (int i=-4; i<=1; i++) {
			if (isOutOfBoard(x+i*shift[0], y+i*shift[1])
					|| board.getPosValue(x+i*shift[0], y+i*shift[1]) == againstPlayer) {
				count = 0;
			} else {
				count++;
			}
		}
		// counts on the other side
		for (int i=1; i<=4; i++) {
			if (isOutOfBoard(x+i*shift[0], y+i*shift[1])
					|| board.getPosValue(x+i*shift[0], y+i*shift[1]) == againstPlayer) {
				break;
			} else {
				count++;
			}
		}
		
		// if count >= 4 there are 5 possible for forPlayer (assuming x,y-stone is forPlayer)
		if (count>=4) {
			return true;
		} else {
			return false;
		}
	}
	
	/*
	 * Main evaluation function
	 * returns an integer rating for the Go-Moku board
	 */
	public int evaluate(Board board, Move lastMove, int prevRating) {
		int lastPlayer = board.getPosValue(lastMove.getX(), lastMove.getY());
		int lastPlayerOpponent;
		int multiplier;
		// set multiplier for rowRatings
		if (lastPlayer == ai ) {
			multiplier = 1;
			lastPlayerOpponent = human;
		} else {
			multiplier = -1;
			lastPlayerOpponent = ai;
		}
		
		// loop through the 3 shifts and check what alterations we did to each line
		for (int[] shift: shifts) {
			/*
			 * Part 1: Alterations for forPlayer
			 * 
			 * First check if 5 of forPlayer's stones are possible in this row.
			 * If not, we didn't change anything since rows wich can't lead to a win
			 * are always rated zero.
			 * 
			 * If 5 of forPlayer's stones are possible in this row
			 * check how we altered lastPlayer's stones
			 * e.g. did we create new lines of more than one stone
			 * or did we add a stone to one of his lines?
			 * 
			 * Therefore, we need to count the stone on each side of the line
			 * and test if the stones are bounded.
			 * 
			 * Then update the rating value accordingly.
			 */
			
			// if lastPlayer didn't place his stone in a potential winning line
			// his move doesn't change anything in this part because everything
			// which does not form a potential winning line is rated zero
			if (isRowOf5Possible(lastMove.getX(), lastMove.getY(), board, shift, lastPlayer)) {
				
				// count how many of lastPlayer's stones are on the left side
				int leftCount = 0;
				for (int i=-1; i>=-4; i--) {
					if (!isOutOfBoard(lastMove.getX()+i*shift[0], lastMove.getY()+i*shift[1])
							&& board.getPosValue(lastMove.getX()+i*shift[0], lastMove.getY()+i*shift[1]) == lastPlayer) {
						leftCount++;
					} else {
						leftCount = 0;
					}
				}
				
				// count how many of lastPlayer's stones are on the right side
				int rightCount = 0;
				for (int i=1; i<=4; i++) {
					if (!isOutOfBoard(lastMove.getX()+i*shift[0], lastMove.getY()+i*shift[1])
							&& board.getPosValue(lastMove.getX()+i*shift[0], lastMove.getY()+i*shift[1]) == lastPlayer) {
						rightCount++;
					} else {
						break;
					}
				}
				
				// check if the previous stones were bounded
				boolean leftBound;
				if (isOutOfBoard(lastMove.getX()-(leftCount+1)*shift[0], lastMove.getY()-(leftCount+1)*shift[1])
					|| board.getPosValue(lastMove.getX()-(leftCount+1)*shift[0], lastMove.getY()-(leftCount+1)*shift[1]) == lastPlayerOpponent) {
					leftBound = true;
				} else {
					leftBound = false;
				}
				boolean rightBound;
				if (isOutOfBoard(lastMove.getX()+(rightCount+1)*shift[0], lastMove.getY()+(rightCount+1)*shift[1])
					|| board.getPosValue(lastMove.getX()+(rightCount+1)*shift[0], lastMove.getY()+(rightCount+1)*shift[1]) == lastPlayerOpponent) {
					rightBound = true;
				} else {
					rightBound = false;
				}
				
				// update value
				prevRating += multiplier*rowRating[1+rightCount+leftCount][((leftBound)?1:0)+((rightBound)?1:0)];
				prevRating -= multiplier*rowRating[leftCount][(leftBound)?1:0];
				prevRating -= multiplier*rowRating[rightCount][(rightBound)?1:0];
			}
			
			/*
			 * Part 2: Alterations for forPlayerOpponent
			 * 
			 * We actually just have to check if forPlayer's move blocks
			 * possible winning lines for forPlayerOpponent
			 */
			
			int count = 0;
			
			// Loops through both sides of the shift line
			for (int i=-1; i<=1; i=i+2) {
				count = 0;
				if (!isOutOfBoard(lastMove.getX()+i*shift[0], lastMove.getY()+i*shift[1]) && board.getPosValue(lastMove.getX()+i*shift[0], lastMove.getY()+i*shift[1]) == free) {
					// x ...
					if (!isOutOfBoard(lastMove.getX()+i*2*shift[0], lastMove.getY()+i*2*shift[1]) && board.getPosValue(lastMove.getX()+i*2*shift[0], lastMove.getY()+i*2*shift[1]) == free) {
						// x  ..
						// do below
					} else if (!isOutOfBoard(lastMove.getX()+i*2*shift[0], lastMove.getY()+i*2*shift[1]) && board.getPosValue(lastMove.getX()+i*2*shift[0], lastMove.getY()+i*2*shift[1]) == lastPlayerOpponent) {
						// x o..
						count++;
						// do below
					} else {
						// x x.. or
						// x |
						continue;
					}
					// x  .. or
					// x o..
					if (!isOutOfBoard(lastMove.getX()+i*3*shift[0], lastMove.getY()+i*3*shift[1]) && board.getPosValue(lastMove.getX()+i*3*shift[0], lastMove.getY()+i*3*shift[1]) == lastPlayerOpponent) {
						// x  o. or
						// x oo.
						count++;
						if (!isOutOfBoard(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1]) && board.getPosValue(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1]) == free) {
							// x  o . or
							// x oo .
							// do below
						} else if (!isOutOfBoard(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1]) && board.getPosValue(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1]) == lastPlayerOpponent) {
							// x  oo or
							// x ooo
							count++;
							// do below
						} else {
							// x  ox or
							// x oox or
							// x  o| or
							// x oo|
							if (count > 1 && !isOutOfBoard(lastMove.getX()-i*shift[0], lastMove.getY()-i*shift[1]) && board.getPosValue(lastMove.getX()-i*shift[0], lastMove.getY()-i*shift[1]) != lastPlayer) {
								// blocked potential winning line
								prevRating += multiplier*rowRating[count][1];
							}
						}
						if (count > 1 && (isOutOfBoard(lastMove.getX()+i*5*shift[0], lastMove.getY()+i*5*shift[1]) || board.getPosValue(lastMove.getX()+i*5*shift[0], lastMove.getY()+i*5*shift[1]) == lastPlayer)) {
							// blocked potential winning line
							prevRating += multiplier*rowRating[count][1];
						}
					} else {
						// x  x. or
						// x   . or
						// x  |  or
						// x ox. or
						// x o . or
						// x o|
					}
				} else if (!isOutOfBoard(lastMove.getX()+i*shift[0], lastMove.getY()+i*shift[1]) && board.getPosValue(lastMove.getX()+i*shift[0], lastMove.getY()+i*shift[1]) == lastPlayerOpponent) {
					// xo...
					if (!isOutOfBoard(lastMove.getX()+i*2*shift[0], lastMove.getY()+i*2*shift[1]) && board.getPosValue(lastMove.getX()+i*2*shift[0], lastMove.getY()+i*2*shift[1]) == free) {
						// xo ..
						if (!isOutOfBoard(lastMove.getX()+i*3*shift[0], lastMove.getY()+i*3*shift[1])
								&& board.getPosValue(lastMove.getX()+i*3*shift[0], lastMove.getY()+i*3*shift[1]) == lastPlayerOpponent
								&& !isOutOfBoard(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1])
								&& board.getPosValue(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1]) == lastPlayerOpponent
								&& (isOutOfBoard(lastMove.getX()+i*5*shift[0], lastMove.getY()+i*5*shift[1])
								|| board.getPosValue(lastMove.getX()+i*5*shift[0], lastMove.getY()+i*5*shift[1]) == lastPlayer)
								) {
							// xo oox or
							// xo oo|
							// blocked potential winning line
							prevRating += multiplier*rowRating[2][1];
						}
					} else if (!isOutOfBoard(lastMove.getX()+i*2*shift[0], lastMove.getY()+i*2*shift[1]) && board.getPosValue(lastMove.getX()+i*2*shift[0], lastMove.getY()+i*2*shift[1]) == lastPlayerOpponent) {
						// xoo..
						if (!isOutOfBoard(lastMove.getX()+i*3*shift[0], lastMove.getY()+i*3*shift[1]) && board.getPosValue(lastMove.getX()+i*3*shift[0], lastMove.getY()+i*3*shift[1]) == free) {
							// xoo .
							if ((!isOutOfBoard(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1])
									&& board.getPosValue(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1]) != lastPlayer)
									|| (!isOutOfBoard(lastMove.getX()-i*shift[0], lastMove.getY()-i*shift[1])
									&& board.getPosValue(lastMove.getX()-i*shift[0], lastMove.getY()-i*shift[1]) != lastPlayer)) {
								// xoo o   or
								// xoo  .  or
								// . xoo . or
								// .oxoo .
								// made open ended 2 to bounded 2
								prevRating += multiplier*rowRating[2][0];
								prevRating -= multiplier*rowRating[2][1];
							}
						} else if (!isOutOfBoard(lastMove.getX()+i*3*shift[0], lastMove.getY()+i*3*shift[1]) && board.getPosValue(lastMove.getX()+i*3*shift[0], lastMove.getY()+i*3*shift[1]) == lastPlayerOpponent) {
							// xooo.
							if (!isOutOfBoard(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1]) && board.getPosValue(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1]) == free) {
								// xooo .
								if (!isOutOfBoard(lastMove.getX()+i*5*shift[0], lastMove.getY()+i*5*shift[1]) && board.getPosValue(lastMove.getX()+i*5*shift[0], lastMove.getY()+i*5*shift[1]) != lastPlayer) {
									// xooo o or
									// xooo  .
									prevRating += multiplier*rowRating[3][0];
									prevRating -= multiplier*rowRating[3][1];
								} else {
									// xooo x
									// xooo |
									// blocked winning line
									prevRating += multiplier*rowRating[3][0];
								}
							} else if (!isOutOfBoard(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1]) && board.getPosValue(lastMove.getX()+i*4*shift[0], lastMove.getY()+i*4*shift[1]) == lastPlayerOpponent) {
								// xoooo
								if (isOutOfBoard(lastMove.getX()+i*5*shift[0], lastMove.getY()+i*5*shift[1]) || board.getPosValue(lastMove.getX()+i*5*shift[0], lastMove.getY()+i*5*shift[1]) == lastPlayer) {
									// xoooox
									// xoooo|
									// blocked line of 4
									prevRating += multiplier*rowRating[4][1];
								} else if (board.getPosValue(lastMove.getX()+i*5*shift[0], lastMove.getY()+i*5*shift[1]) == free) {
									// xoooo 
									prevRating += multiplier*rowRating[4][0];
									prevRating -= multiplier*rowRating[4][1];
								}
							} else {
								// xooox or
								// xooo|
								if (!isOutOfBoard(lastMove.getX()-i*shift[0], lastMove.getY()-i*shift[1]) && board.getPosValue(lastMove.getX()-i*shift[0], lastMove.getY()-i*shift[1]) != lastPlayer) {
									// blocked line of 3
									prevRating += multiplier*rowRating[3][1];
								}
							}
						} else {
							// xoox. or
							// xoo|.
							if (!isOutOfBoard(lastMove.getX()-i*shift[0], lastMove.getY()-i*shift[1])
									&& !isOutOfBoard(lastMove.getX()-i*2*shift[0], lastMove.getY()-i*2*shift[1])
									&& board.getPosValue(lastMove.getX()-i*shift[0], lastMove.getY()-i*shift[1]) != lastPlayer
									&& board.getPosValue(lastMove.getX()-i*2*shift[0], lastMove.getY()-i*2*shift[1]) != lastPlayer) {
								// .  xoo| or
								// . oxoo| or
								// .ooxoo| or
								// .o xoo|
								// blocked potential winning line
								prevRating += multiplier*rowRating[2][1];
							}
						}
					} else {
						// xox..
						// xo|
						// do nothing
					}
				} else {
					// xx... or
					// x|
					// do nothing
				}
			}
		}
		return 0;
	}
}
